#include <assert.h>
#include <stddef.h>
#include <string.h>
#include <stdlib.h>
#include "ringbuf.h"

// static unsigned int rounddown_pow_of_two(unsigned int n) {
// 	n|=n>>1; n|=n>>2; n|=n>>4; n|=n>>8; n|=n>>16;
// 	return (n+1) >> 1;
// }

static unsigned int inline roundup_pow_of_two(unsigned int v)
{
	v--;
	v |= v >> 1;
	v |= v >> 2;
	v |= v >> 4;
	v |= v >> 8;
	v |= v >> 16;
	v++;
	return v;
}

bool init_ringbuf(struct ringbuf_t *self, unsigned int item_size, unsigned int item_count)
{
	unsigned int buf_size = roundup_pow_of_two(item_count);
	self->data = malloc(buf_size * item_size);
	if(NULL == self->data){
		return false;
	}
	self->esize = item_size;
	self->mask = buf_size - 1;
	self->in = self->out = 0;
	return true;
}

void free_ringbuf(struct ringbuf_t *rb)
{
	if (rb->data)
	{
		free(rb->data);
		rb->data = NULL;
	}
}

// item count in buf
unsigned int inline ringbuf_len(const struct ringbuf_t *self) {
    return self->in - self->out;
};

// max item count in buf
unsigned int inline ringbuf_cap(const struct ringbuf_t *self) {
    return self->mask + 1;
};

// avail item count
unsigned int inline ringbuf_avail(const struct ringbuf_t *self) {
    return ringbuf_cap(self) - ringbuf_len(self);
};

bool inline ringbuf_is_full(const struct ringbuf_t *self) {
    return ringbuf_len(self) > self->mask;
};

bool inline ringbuf_is_empty(const struct ringbuf_t *self) {
    return self->in == self->out;
};

static void inline ringbuf_copy_in(struct ringbuf_t *self, const void *src, unsigned int len, unsigned int off) {
	unsigned int size = self->mask + 1;
	unsigned int esize = self->esize;
	unsigned int l;

	off &= self->mask;
	if (esize != 1) {
		off *= esize;
		size *= esize;
		len *= esize;
	}
	l = len < (size - off) ? len : (size - off);

	memcpy(self->data + off, src, l);
	memcpy(self->data, (const unsigned char*)src + l, len - l);

	/*
	 * make sure that the data in the fifo is up to date before
	 * incrementing the fifo->in index counter
	 */
	smp_wmb();
}

unsigned int ringbuf_in(struct ringbuf_t *self, const void *buf, unsigned int item_count) {
    unsigned int avail = ringbuf_avail(self);
    if (item_count > avail)
        item_count = avail;

    ringbuf_copy_in(self, buf, item_count, self->in);

    self->in += item_count;
    return item_count;
}

static void inline ringbuf_copy_out(struct ringbuf_t *self, void *dst, unsigned int len, unsigned int off) {
	unsigned int size = self->mask + 1;
	unsigned int esize = self->esize;
	unsigned int l;

	off &= self->mask;
	if (esize != 1) {
		off *= esize;
		size *= esize;
		len *= esize;
	}
	l = len < (size - off) ? len : (size - off);

	memcpy(dst, self->data + off, l);
	memcpy((unsigned char*)dst + l, self->data, len - l);
	/*
	 * make sure that the data is copied before
	 * incrementing the fifo->out index counter
	 */
	smp_wmb();
}

unsigned int ringbuf_out_peek(struct ringbuf_t *self, void *buf, unsigned int len) {
	unsigned int l;
	l = self->in - self->out;
	if (len > l) {
		len = l;
	}
	ringbuf_copy_out(self, buf, len, self->out);
	return len;
}

unsigned int ringbuf_out(struct ringbuf_t *self, void *buf, unsigned int item_count) {
    item_count = ringbuf_out_peek(self, buf, item_count);
    self->out += item_count;
    return item_count;
}
